from __future__ import annotations

import json
import os
from dataclasses import asdict, dataclass, field
from enum import Enum, unique

from codesecurity.data import copy_files_in_parallel
from codesecurity.data.objects import ProgramLanguage


@dataclass
class SourceInfo:
    origin_path:str=""
    size:int=0
    lang:ProgramLanguage=ProgramLanguage.Others

@dataclass
class AuthorInfo:
    login: str
    id: int
    node_id: str
    avatar_url: str
    gravatar_id: str
    url: str
    html_url: str
    followers_url: str
    following_url: str
    gists_url: str
    starred_url: str
    subscriptions_url: str
    organizations_url: str
    repos_url: str
    events_url: str
    received_events_url: str
    type: str
    site_admin: bool
    name: str
    company: str
    blog: str
    location: str
    email: str
    hireable: bool
    bio: str
    twitter_username: str
    public_repos: int
    public_gists: int
    followers: int
    following: int
    created_at: str
    updated_at: str

@dataclass
class LicenseInfo:
    key: str
    name: str
    spdx_id: str
    url: str
    node_id: str

@dataclass
class PermissionsInfo:
    admin: bool
    maintain: bool
    push: bool
    triage: bool
    pull: bool

@dataclass
class RepositoryInfo:
    id: int
    node_id: str
    name: str
    full_name: str
    private: bool
    owner: AuthorInfo
    html_url: str
    description: str
    fork: bool
    url: str
    forks_url: str
    keys_url: str
    collaborators_url: str
    teams_url: str
    hooks_url: str
    issue_events_url: str
    events_url: str
    assignees_url: str
    branches_url: str
    tags_url: str
    blobs_url: str
    git_tags_url: str
    git_refs_url: str
    trees_url: str
    statuses_url: str
    languages_url: str
    stargazers_url: str
    contributors_url: str
    subscribers_url: str
    subscription_url: str
    commits_url: str
    git_commits_url: str
    comments_url: str
    issue_comment_url: str
    contents_url: str
    compare_url: str
    merges_url: str
    archive_url: str
    downloads_url: str
    issues_url: str
    pulls_url: str
    milestones_url: str
    notifications_url: str
    labels_url: str
    releases_url: str
    deployments_url: str
    created_at: str
    updated_at: str
    pushed_at: str
    git_url: str
    ssh_url: str
    clone_url: str
    svn_url: str
    homepage: str
    size: int
    stargazers_count: int
    watchers_count: int
    language: str
    has_issues: bool
    has_projects: bool
    has_downloads: bool
    has_wiki: bool
    has_pages: bool
    has_discussions: bool
    forks_count: int
    mirror_url: str
    archived: bool
    disabled: bool
    open_issues_count: int
    license: LicenseInfo
    allow_forking: bool
    is_template: bool
    web_commit_signoff_required: bool
    topics: list
    visibility: str
    forks: int
    open_issues: int
    watchers: int
    default_branch: str
    permissions: PermissionsInfo

class Source:
    def __init__(self,path,source_info:SourceInfo) -> None:
        self.path=path
        self.source_info=source_info
        self._text=None
        
    def read(self,encoding='utf-8'):
        with open(self.path,'r',encoding=encoding,errors='ignore') as f:
            return f.read(-1)

    def migrate(self,out_file):
        self.prepare_migrate_dest(out_file)
        with open(out_file,'w',encoding='utf-8') as f:
            f.write(self.read())

    def prepare_migrate_dest(self,out_file):
        dir_name=os.path.dirname(out_file)
        os.makedirs(dir_name,exist_ok=True)
    
    @property
    def size(self):
        return self.source_info.size
    
    @property
    def text(self):
        if self._text is None:
            self._text=self.read()
            
        return self._text
    
class Repository:
    def __init__(self,sources:list[Source],repo_info:RepositoryInfo,local_dir) -> None:
        self.sources=sources
        self.repo_info=repo_info
        self.local_dir=local_dir

    def migrate(self,out_dir):
        if not os.path.exists(out_dir):
            os.makedirs(out_dir)
        
        origin2dest=[]
            
        for source in self.sources:
            source_new_path=os.path.join(out_dir,source.source_info.origin_path)
            source.prepare_migrate_dest(source_new_path)
            origin2dest.append((source.path,source_new_path))
            
        result=copy_files_in_parallel(origin2dest)
        return result
    
    def filter(self,lang:ProgramLanguage,limit=None):
        sources=[source for source in self.sources if source.source_info.lang==lang]
        
        if limit is not None:
            sources=sources[:min(limit,len(sources))]

        return Repository(sources,self.repo_info,self.local_dir)
    
    @property
    def size(self):
        return sum([e.size for e in self.sources])

class Author:
    RepoMeta='repos_info.json'
    def __init__(self,repos:list[Repository],author_info:AuthorInfo,local_dir) -> None:
        self.repos=repos
        self.author_info=author_info
        self.local_dir=local_dir
        
    def migrate(self,out_dir):
        new_author_dir=os.path.join(out_dir,self.author_info.login)
        repo_infos=[]
        author_info=asdict(self.author_info)
        
        if not os.path.exists(new_author_dir):
            os.makedirs(new_author_dir)
        
        for repo in self.repos:
            repo.migrate(new_author_dir)
            repo_infos.append(asdict(repo.repo_info))
        
        with open(os.path.join(new_author_dir,Author.RepoMeta),'w') as f:
            json.dump(repo_infos,f,indent=4)
            
        with open(os.path.join(new_author_dir,f'{self.author_info.login}.json'),'w') as f:
            json.dump(author_info,f,indent=4)

    @property
    def file_number(self):
        return sum([len(e.sources) for e in self.repos])

    def filter(self,lang:ProgramLanguage,limit=None):
        repos=[repo.filter(lang,limit) for repo in self.repos]
        
        return Author(repos,self.author_info,self.local_dir)
    
    @property
    def size(self):
        return sum([e.size for e in self.repos])
    

@dataclass
class RepoVectorRow:
    url:str=""
    type:str=""
    vector:list[float]=field(default_factory=list)
    create_time:str=""
    update_time:str=""

@dataclass
class FileVectorRow:
    url:str=""
    type:str=""
    vector:list[float]=field(default_factory=list)
    create_time:str=""
    update_time:str=""